package svanimpe.reminders.resources;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Calendar;
import java.util.Set;
import javax.annotation.Resource;
import javax.enterprise.context.RequestScoped;
import javax.json.Json;
import javax.json.JsonException;
import javax.json.JsonObject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.transaction.Transactional;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import svanimpe.reminders.domain.List;
import svanimpe.reminders.domain.Location;
import svanimpe.reminders.domain.Reminder;
import svanimpe.reminders.domain.Role;
import static svanimpe.reminders.util.Utilities.IMAGES_BASE_DIR;
import static svanimpe.reminders.util.Utilities.MAX_IMAGE_SIZE_IN_MB;
import static svanimpe.reminders.util.Utilities.mergeMessages;
@Path("lists/{listid}/reminders")
@Transactional(dontRollbackOn = {BadRequestException.class, ForbiddenException.class, NotFoundException.class})
@RequestScoped
public class Reminders
{
@PersistenceContext
private EntityManager em;
@Resource
private Validator validator;
@Context
private SecurityContext context;
@GET
@Produces(MediaType.APPLICATION_JSON)
public java.util.List<Reminder> getRemindersInList(@PathParam("listid") long listId)
{
List list = em.find(List.class, listId);
if (list == null) {
throw new NotFoundException();
}
// Only admins can read another user's reminders.
if (!list.getOwner().getUsername().equals(context.getUserPrincipal().getName()) && !context.isUserInRole(Role.ADMINISTRATOR.name())) {
throw new ForbiddenException();
}
TypedQuery<Reminder> q = em.createNamedQuery("Reminder.findByList", Reminder.class).setParameter("list", list);
return q.getResultList();
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response addReminderToList(@PathParam("listid") long listId, Reminder reminder)
{
List list = em.find(List.class, listId);
if (list == null) {
throw new NotFoundException();
}
// Only admins can add reminders to another user's lists.
if (!list.getOwner().getUsername().equals(context.getUserPrincipal().getName()) && !context.isUserInRole(Role.ADMINISTRATOR.name())) {
throw new ForbiddenException();
}
reminder.setList(list);
Set<ConstraintViolation<Reminder>> violations = validator.validate(reminder);
if (!violations.isEmpty()) {
throw new BadRequestException(mergeMessages(violations));
}
em.persist(reminder);
return Response.created(URI.create("/lists/" + listId + "/reminders/" + reminder.getId())).build();
}
@GET
@Path("{reminderid}")
@Produces(MediaType.APPLICATION_JSON)
public Reminder getReminder(@PathParam("listid") long listId, @PathParam("reminderid") long reminderId)
{
Reminder reminder = em.find(Reminder.class, reminderId);
if (reminder == null || reminder.getList().getId() != listId) {
throw new NotFoundException();
}
// Only admins can read another user's reminders.
if (!reminder.getList().getOwner().getUsername().equals(context.getUserPrincipal().getName()) && !context.isUserInRole(Role.ADMINISTRATOR.name())) {
throw new ForbiddenException();
}
return reminder;
}
@PUT
@Path("{reminderid}")
@Consumes(MediaType.APPLICATION_JSON)
public void updateReminder(@PathParam("listid") long listId, @PathParam("reminderid") long reminderId, InputStream in)
{
Reminder reminder = em.find(Reminder.class, reminderId);
if (reminder == null || reminder.getList().getId() != listId) {
throw new NotFoundException();
}
// Only admins can update another user's reminders.
if (!reminder.getList().getOwner().getUsername().equals(context.getUserPrincipal().getName()) && !context.isUserInRole(Role.ADMINISTRATOR.name())) {
throw new ForbiddenException();
}
em.detach(reminder);
try {
JsonObject reminderUpdate = Json.createReader(in).readObject();
if (reminderUpdate.containsKey("list")) {
List list = em.find(List.class, reminderUpdate.getJsonNumber("list").longValue());
// Only admins can move reminders to another user's list.
if (!list.getOwner().getUsername().equals(context.getUserPrincipal().getName()) && !context.isUserInRole(Role.ADMINISTRATOR.name())) {
throw new ForbiddenException();
} else {
reminder.setList(list);
}
}
if (reminderUpdate.containsKey("title")) {
reminder.setTitle(reminderUpdate.getString("title"));
}
if (reminderUpdate.containsKey("date")) {
if (reminderUpdate.isNull("date")) {
reminder.setDate(null);
} else {
Calendar date = Calendar.getInstance();
date.setTimeInMillis(reminderUpdate.getJsonNumber("date").longValue());
reminder.setDate(date);
}
}
if (reminderUpdate.containsKey("location")) {
if (reminderUpdate.isNull("location")) {
reminder.setLocation(null);
System.out.println("--- SET TO NULL");
} else {
Location location = new Location();
JsonObject jsonLocation = reminderUpdate.getJsonObject("location");
if (jsonLocation.containsKey("latitude")) {
location.setLatitude(jsonLocation.getJsonNumber("latitude").doubleValue());
}
if (jsonLocation.containsKey("longitude")) {
location.setLongitude(jsonLocation.getJsonNumber("longitude").doubleValue());
}
reminder.setLocation(location);
}
}
} catch (JsonException | ClassCastException ex) {
// Invalid JSON or type mismatch.
throw new BadRequestException("JSON");
}
Set<ConstraintViolation<Reminder>> violations = validator.validate(reminder);
if (!violations.isEmpty()) {
throw new BadRequestException(mergeMessages(violations));
}
em.merge(reminder);
}
@DELETE
@Path("{reminderid}")
public void removeReminder(@PathParam("listid") long listId, @PathParam("reminderid") long reminderId) throws IOException
{
Reminder reminder = em.find(Reminder.class, reminderId);
if (reminder == null || reminder.getList().getId() != listId) {
throw new NotFoundException();
}
// Only admins can delete another user's reminders.
if (!reminder.getList().getOwner().getUsername().equals(context.getUserPrincipal().getName()) && !context.isUserInRole(Role.ADMINISTRATOR.name())) {
throw new ForbiddenException();
}
if (reminder.getImage() != null) {
Files.deleteIfExists(IMAGES_BASE_DIR.resolve(reminder.getImage()));
}
em.remove(reminder);
}
@GET
@Path("{reminderid}/image")
@Produces("image/jpeg")
public InputStream getImage(@PathParam("listid") long listId, @PathParam("reminderid") long reminderId) throws IOException
{
Reminder reminder = em.find(Reminder.class, reminderId);
if (reminder == null || reminder.getList().getId() != listId || reminder.getImage() == null) {
throw new NotFoundException();
}
java.nio.file.Path path = IMAGES_BASE_DIR.resolve(reminder.getImage());
if (!Files.exists(path)) {
throw new InternalServerErrorException("Could not load image " + reminder.getImage());
}
return Files.newInputStream(path);
}
@PUT
@Path("{reminderid}/image")
@Consumes("image/jpeg")
public void setImage(@PathParam("listid") long listId, @PathParam("reminderid") long reminderId, @HeaderParam("Content-Length") long fileSize, InputStream in) throws IOException
{
Reminder reminder = em.find(Reminder.class, reminderId);
if (reminder == null || reminder.getList().getId() != listId) {
throw new NotFoundException();
}
// Only admins can update another user's images.
if (!context.getUserPrincipal().getName().equals(reminder.getList().getOwner().getUsername()) && !context.isUserInRole(Role.ADMINISTRATOR.name())) {
throw new ForbiddenException();
}
// Make sure the file is not larger than the maximum allowed size.
if (fileSize > 1024 * 1024 * MAX_IMAGE_SIZE_IN_MB) {
throw new BadRequestException("REMINDER_IMAGE");
}
// Save the image. By default, {reminderid}.jpg is used as the filename.
Files.copy(in, IMAGES_BASE_DIR.resolve(reminder.getId() + ".jpg"), StandardCopyOption.REPLACE_EXISTING);
reminder.setImage(reminder.getId() + ".jpg");
}
@DELETE
@Path("{reminderid}/image")
public void removeImage(@PathParam("listid") long listId, @PathParam("reminderid") long reminderId) throws IOException
{
Reminder reminder = em.find(Reminder.class, reminderId);
if (reminder == null || reminder.getList().getId() != listId || reminder.getImage() == null) {
throw new NotFoundException();
}
// Only admins can delete another user's images.
if (!context.getUserPrincipal().getName().equals(reminder.getList().getOwner().getUsername()) && !context.isUserInRole(Role.ADMINISTRATOR.name())) {
throw new ForbiddenException();
}
Files.deleteIfExists(IMAGES_BASE_DIR.resolve(reminder.getImage()));
reminder.setImage(null);
}
}